#include "LightingCommon.fxh"

cbuffer cbPerFrame
{
	float4x4 gProj;
};

cbuffer cbPerLight
{
	float4x4 gWorldView;
	float3 gLightPosV;
	float3 gLightSpotDirV;
	float4 gLightColorAndIntens;
	float4 gLightParams; // x - radius, y - falloff, z - cosinus of spot cone angle, w - spot falloff
#ifdef SHADOWS
	// Since in PS pixel's view space position is obtained, in order to draw shadows
	// it is necessary to transform it to light projection space, in following order:
	// view space -> world space -> light space -> light projection space
	// Entire transformations is in this matrix
	float4x4 gInvView_LightViewProj;
#endif
};

#ifdef SHADOWS
SamplerComparisonState ShadowSampler
{
   // sampler state
   Filter = MIN_MAG_LINEAR_MIP_POINT;
   AddressU = MIRROR;
   AddressV = MIRROR;

   // sampler comparison state
   ComparisonFunc = LESS;
   Filter = COMPARISON_MIN_MAG_LINEAR_MIP_POINT;
};

Texture2D gShadowMap;
#endif

struct VS_IN
{
	float3 posL     : POSITION;
};

struct VS_OUT
{
	float4 posD         : SV_POSITION;
	noperspective float2 posH         : TEXCOORD0;
	float3 viewRayV     : TEXCOORD1;
};

VS_OUT VS(VS_IN vIn)
{
	VS_OUT vOut;

	float4 posV = mul(float4(vIn.posL, 1.0f), gWorldView);
	float4 posH = mul(posV, gProj);

	vOut.posD = posH;

	// SV_POSITION interpolator will be in window coords in PS, so another copy in [0;1] is useful
	vOut.posH = (posH / posH.w).xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f);

	// view space vertex position is the same thing as view ray in view space
	vOut.viewRayV = posV.xyz;
	
	return vOut;
}

#ifdef SHADOWS
float calculateShadowFactor(float4 lightSpacePos)
{
	lightSpacePos = lightSpacePos / lightSpacePos.w;

	const float depth = lightSpacePos.z;
	const float2 smTexCoord = (lightSpacePos.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f));

	const float smTexelSize = 1.0f / 512.0f;

	float shadowFactor = 0.0f;
	const int pcfKernelHalfSize = 1;
	const float pcfKernelNumSamplesInv = 1.0f / ( (2 * pcfKernelHalfSize + 1) * (2 * pcfKernelHalfSize + 1) );
	for(float x = -pcfKernelHalfSize; x <= pcfKernelHalfSize; x += 1.f)
	{
		for(float y = -pcfKernelHalfSize; y <= pcfKernelHalfSize; y += 1.f)
		{
			shadowFactor += gShadowMap.SampleCmpLevelZero(ShadowSampler, smTexCoord + float2(x, y) * smTexelSize, depth);
		}
	}
	shadowFactor *= pcfKernelNumSamplesInv;
	
	//float shadowFactor = shadowMap.SampleCmpLevelZero(ShadowSampler, smTexCoord, depth);

	return shadowFactor;
}
#endif

float4 PS(VS_OUT pIn) : SV_Target
{
	const float3 viewRayV = normalize(pIn.viewRayV);
	
	const float2 texCoord = pIn.posH;
	const float3 posV = viewRayV * gDepthTex.Sample(gPointSam, texCoord).r;

	float3 lightDirV = gLightPosV - posV;
	float lightDist = length(lightDirV);
	lightDirV = normalize(lightDirV);

	float spotAngleCos = saturate(dot(-gLightSpotDirV, lightDirV));

	clip(spotAngleCos - gLightParams.z); // if spotAngleCos < gLightParams.z

	float shadowFactor = 1.0f;

#ifdef SHADOWS
	float4 lightSpacePos = mul(float4(posV, 1.0f), gInvView_LightViewProj);
	shadowFactor = calculateShadowFactor(lightSpacePos);
	
	//clip(shadowFactor - 0.01f); // ??? faster ???
#endif

	float3 normalV, diffColor;
	float specIntens, specPower;
	getPixelParamsFromGBuffer(texCoord, normalV, diffColor, specIntens, specPower);
	
	float lightRadius = gLightParams.x;
	float atten = saturate((lightRadius - lightDist) / lightRadius);
	float angleAtten = (spotAngleCos - gLightParams.z) / (1.0 - gLightParams.z);

	float3 lighting = calculateLighting(viewRayV, lightDirV, normalV, gLightColorAndIntens, diffColor, specIntens, specPower);
	return float4(shadowFactor * angleAtten * atten * lighting, 1.0f);
}

technique11 tech
{
	pass P0
	{
		SetVertexShader( CompileShader( vs_5_0, VS() ) );
		SetGeometryShader( NULL );
		SetPixelShader( CompileShader( ps_5_0, PS() ) );
		
		SetDepthStencilState(DepthOff, 0);
		SetBlendState(Additive, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF);
		SetRasterizerState(CullFront);
	}
}
